/**
 * Copyright 2017 orivil Authors. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package mux

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"strings"
	"net/http"
	"net/url"
	"gitee.com/tugoer/orivil/service"
	"gitee.com/tugoer/orivil/param"
	"gitee.com/tugoer/orivil/session"
	trace2 "gitee.com/tugoer/orivil/trace"
)

const (
	defaultMaxMemory = 200 << 20 // 32 MB
)

type ErrCode int

func (ec ErrCode) String() string {
	return fmt.Sprintf("%03d", ec)
}

const (
	ErrWarning ErrCode = 100 + iota
	ErrDanger
	ErrEmergency
)

type codeError struct {
	code ErrCode
	err  error
}

func (e *codeError) Error() string {
	return fmt.Sprintf("code: %03d, error: %s", e.code, e.err)
}

type MsgType int

const (
	MsgSuccess MsgType = 1 << iota
	MsgInfo
	MsgWarning
	MsgDanger
)

func (mt MsgType) String() string {
	switch mt {
	case MsgSuccess:
		return "success"
	case MsgInfo:
		return "info"
	case MsgWarning:
		return "warning"
	case MsgDanger:
		return "danger"
	default:
		return "unknown"
	}
}

type Context struct {
	// default context
	context.Context

	// service container is an dependency management container.
	*service.Container

	// http response writer
	Response http.ResponseWriter

	// http request object
	*http.Request

	parser *param.Parameters

	// path parameter
	PathParams url.Values

	// memory session object
	session *session.Session

	sessionClient *session.Client

	// cache for client URL query
	query url.Values

	// cache for form value and query value
	form url.Values

	vDir string
	// view files
	views []string

	// view data
	viewData map[string]interface{}

	defers []func()

	//cancel chan *codeError

	Values url.Values
}

var (
	ResponseProvider service.Provider
	ContextProvider  service.Provider
	RequestProvider  service.Provider
	SessionProvider service.Provider = func(c *service.Container) interface{} {
		ctx := c.Get(&ContextProvider).(*Context)
		if ctx.sessionClient != nil {
			session, err := ctx.sessionClient.GetHttpSession(ctx.Response, ctx.Request)
			if err != nil {
				ctx.ErrEmergency(err)
			} else {
				return session
			}
		} else {
			ctx.ErrEmergency(fmt.Errorf("session rpc client is nil"))
		}
		return nil
	}
)

func initContext(w http.ResponseWriter, r *http.Request, parser *param.Parameters, pathParams url.Values, client *session.Client) *Context {
	r.Body = http.MaxBytesReader(w, r.Body, Env.Server.LimitPostBody)
	container := service.NewContainer(false)
	c := &Context{
		Context:    r.Context(),
		Container:  container,
		Response:   w,
		Request:    r,
		PathParams: pathParams,
		parser:     parser,
		sessionClient: client,
		viewData:   make(map[string]interface{}),
		Values:     make(url.Values),
	}
	r.WithContext(c)
	// cache base service
	container.SetCache(&ResponseProvider, w)
	container.SetCache(&RequestProvider, r)
	container.SetCache(&ContextProvider, c)
	return c
}

func (ctx *Context) Error(err error, code ErrCode, trace int) {
	if err != nil {
		if trace > 0 {
			err = trace2.DeepError(err, trace + 1, 1)
		}
		// TODO: 考虑是否开启协程？使用 channel 传递 error, 并是使用 runtime.Goexit() 退出当前协程
		panic(&codeError{code: code, err: err})
	}
}

func (ctx *Context) ErrEmergency(err error) {
	if err != nil {
		ctx.Error(err, ErrEmergency, 2)
	}
}

func (ctx *Context) ErrWarning(err error) {
	if err != nil {
		ctx.Error(err, ErrWarning,2)
	}
}

func (ctx *Context) ErrDanger(err error) {
	if err != nil {
		ctx.Error(err, ErrDanger,2)
	}
}

// MustParse 解析参数时如果发生错误将发生一个 ErrDanger 类型错误
func (ctx *Context) MustParse(schemas ...interface{}) (msg, id string) {
	var err error
	msg, id, err = ctx.Parse(schemas...)
	if err != nil {
		ctx.Error(err, ErrDanger,2)
	}
	return
}

// WarnParse 如果验证失败将发送一个 Warning Message
func (ctx *Context) WarnParse(schemas ...interface{}) {
	msg, id, err := ctx.Parse(schemas...)
	if err != nil {
		ctx.Error(err, ErrDanger,2)
	} else {
		if msg != "" {
			ctx.MsgWarning(msg, id)
		}
	}
	return
}

// InfoParse 如果验证失败将发送一个 Info Message
func (ctx *Context) InfoParse(schemas ...interface{}) {
	msg, id := ctx.MustParse(schemas...)
	if msg != "" {
		ctx.MsgInfo(msg, id)
	}
	return
}

// DangerParse 如果验证失败将发送一个 Danger Message
func (ctx *Context) DangerParse(schemas ...interface{}) {
	msg, id := ctx.MustParse(schemas...)
	if msg != "" {
		ctx.MsgDanger(msg, id)
	}
	return
}

// TODO: i18n message
// 验证并解析模型参数, 模型在使用之前必须注册, 返回验证消息, 消息字段id, 以及解析结果
func (ctx *Context) Parse(schemas ...interface{}) (msg, id string, err error) {
	msg, id, err = ctx.parser.ValidAndParseHttp(ctx.Request, ctx.PathParams, schemas...)
	if err != nil {
		if strings.Contains(err.Error(), "strconv") {
			msg = "参数格式错误"
			err = nil
		}
	}
	return
}

// MaxBytes for limiting the request body
func (ctx *Context) SetMaxBytesBody(n int64) {
	ctx.Request.Body = http.MaxBytesReader(ctx.Response, ctx.Request.Body, n)
}

// 响应 302 跳转, 并返回当前 http 协程
func (ctx *Context) Redirect(url string) {
	http.Redirect(ctx.Response, ctx.Request, url, 302)
	ctx.Cancel()
}

// 跳转至前一个页面, 如果失败则跳转至指定页面
func (ctx *Context) RedirectBack(url string) {
	referer := ctx.Request.Header.Get("Referer")
	if referer == "" {
		if url == "" {
			url = "/"
		}
		referer = url
	}
	ctx.Redirect(referer)
}

// 立即返回当前 http 协程
func (ctx *Context) Cancel() {
	panic(nil)
}

// 保存一个或多个上传文件。
//
// Deprecated: 建议使用 Parse() 系列方法替代, Parse() 系列方法可生成 API 文档, 且可验证数据
func (ctx *Context) StoreUploadFiles(handler param.FileHandler) error {
	if ctx.Request.MultipartForm == nil {
		// parse MIME type "multipart/form-data"
		err := ctx.Request.ParseMultipartForm(defaultMaxMemory)
		if err != nil {
			return err
		}
		ctx.form = ctx.Request.Form
		if ctx.form == nil {
			ctx.form = make(url.Values)
		}
	}
	// range files
	for field, headers := range ctx.Request.MultipartForm.File {
		for _, header := range headers {
			err := handler(field, header)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Form 用于获得表单数据以及 url query 参数, 该方法将自动解析数据类型为: "application/x-www-form-urlencoded"
// 的数据并缓存. 如果表单数据类型为 "multipart/form-data", 使用之前需要调用 ctx.Request.ParseFormFiles() 方法.
//
// Deprecated: 建议使用 Parse() 系列方法替代, Parse() 系列方法可生成 API 文档, 且可验证数据
func (ctx *Context) Form() url.Values {
	if ctx.form == nil {
		if ctx.Request.Form != nil {
			ctx.form = ctx.Request.Form
		} else {
			// parse MIME type "application/x-www-form-urlencoded"
			err := ctx.Request.ParseForm()
			ctx.Error(err, ErrDanger,2)
			ctx.form = ctx.Request.Form
		}
	}
	return ctx.form
}

// Query 用于获得 url query 参数.
//
// Deprecated: 建议使用 Parse() 系列方法替代, Parse() 系列方法可生成 API 文档, 且可验证数据
func (ctx *Context) Query() url.Values {
	if ctx.query == nil {
		ctx.query = ctx.Request.URL.Query()
	}
	return ctx.query
}

// 设置 template 文件所在目录. e.g. "admin" 则将渲染 "views/admin" 目录下的文件.
func (ctx *Context) VDir(subDir string) *Context {
	ctx.vDir = subDir
	return ctx
}

// 渲染模板文件, 可以带目录名. e.g. "admin/login" 则将渲染 "views/admin/login.gohtml" 文件.
func (ctx *Context) View(name ...string) *Context {
	ctx.views = append(ctx.views, name...)
	return ctx
}

// alias for Cancel()
func (ctx *Context) Flush() {
	ctx.Cancel()
}

// 模板数据参数
func (ctx *Context) With(name string, data interface{}) {
	ctx.viewData[name] = data
}

// 发送 json 格式消息, 调用该方法会自动结束当前协程
func (ctx *Context) Message(typ MsgType, msg, id string, cancel bool) {
	SendMessage(typ, msg, id, ctx.Response)
	if cancel {
		ctx.Cancel()
	}
}

// 发送 json 数据
func SendJson(data interface{}, writer http.ResponseWriter, escapeHtml bool) error {
	// before send to client, check the data format
	buf := bytes.NewBuffer(nil)
	encoder := json.NewEncoder(buf)
	encoder.SetIndent("", "\t")
	encoder.SetEscapeHTML(escapeHtml)
	err := encoder.Encode(data)
	if err != nil {
		return err
	}
	writer.Header().Set("Content-Type", "application/json;charset=UTF-8")
	_, err = writer.Write(buf.Bytes())
	return err
}

// 发送 xml 数据
func SendXml(data interface{}, writer http.ResponseWriter) error {
	// before send to client, check the data format
	buf := bytes.NewBuffer(nil)
	err := xml.NewEncoder(buf).Encode(data)
	if err != nil {
		return err
	}
	writer.Header().Set("Content-Type", "application/xml;charset=UTF-8")
	_, err = writer.Write(buf.Bytes())
	return err
}

// 发送消息类型 json 数据
func SendMessage(typ MsgType, msg, id string, writer http.ResponseWriter) {
	data := map[string]string{
		"MsgContent": msg,
		"MsgType":    typ.String(),
		"MsgID":      id,
	}
	// TODO: message code 不可使用 200 等常用 code, 暂时使用 202 代替
	writer.WriteHeader(http.StatusAccepted)
	// ignore error
	SendJson(data, writer, false)
}

func (ctx *Context) MsgDanger(msg, id string) {
	ctx.Message(MsgDanger, msg, id, true)
}

func (ctx *Context) MsgInfo(msg, id string) {
	ctx.Message(MsgInfo, msg, id, true)
}

func (ctx *Context) MsgSuccess(msg, id string) {
	ctx.Message(MsgSuccess, msg, id, true)
}

func (ctx *Context) MsgWarning(msg, id string) {
	ctx.Message(MsgWarning, msg, id, true)
}

func (ctx *Context) SendXml(data interface{}) {
	err := SendXml(data, ctx.Response)
	ctx.Error(err, ErrWarning, 2)
	ctx.Cancel()
}

func (ctx *Context) SendNamedJson(name string, data interface{}) {
	ctx.SendJson(map[string]interface{}{
		name: data,
	})
}

func (ctx *Context) SendJson(data interface{}) {
	err := SendJson(data, ctx.Response, true)
	ctx.Error(err, ErrWarning, 2)
	ctx.Cancel()
}

func (ctx *Context) Session() *session.Session {
	if ctx.session == nil {
		ctx.session = ctx.Container.Get(&SessionProvider).(*session.Session)
	}
	return ctx.session
}

func (ctx *Context) SetCookie(c *http.Cookie) {
	http.SetCookie(ctx.Response, c)
}

func (ctx *Context) WriteString(str string) error {
	_, err := ctx.Response.Write([]byte(str))
	return err
}

func (ctx *Context) Write(data []byte) error {
	_, err := ctx.Response.Write(data)
	return err
}

func (ctx *Context) Header() http.Header {
	return ctx.Response.Header()
}

func (ctx *Context) WriteHeader(code int) {
	ctx.Response.WriteHeader(code)
}

func (ctx *Context) Defer(f func()) {
	ctx.defers = append(ctx.defers, f)
}

func (ctx *Context) GetIP() string {
	return GetIP(ctx.Request)
}

// TODO: 待验证
func GetIP(request *http.Request) string {
	addr := request.Header.Get("X-Real-IP")
	if addr == "" {
		addr = request.Header.Get("X-Forwarded-For")
		if addr == "" {
			addr = request.RemoteAddr
		} else {
			addrs := strings.Split(addr, ",")
			for _, value := range addrs {
				if len(value) > 0 {
					addr = value
					break
				}
			}
		}
	}
	if index := strings.Index(addr, ":"); index > 0 {
		addr = addr[:index]
	}
	return addr
}

func (ctx *Context) NotFound() {
	http.NotFound(ctx.Response, ctx.Request)
}

// 允许跨域访问
func (ctx *Context) AllowCrossSiteAccess() {
	ctx.Response.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	ctx.Response.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
	ctx.Response.Header().Set("Access-Control-Allow-Origin", "*")
}
